package com.amap.flutter.map.overlays.marker;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;

import com.amap.api.maps.AMap;
import com.amap.api.maps.TextureMapView;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.Poi;
import com.amap.api.maps.model.Polyline;
import com.amap.flutter.map.MyMethodCallHandler;
import com.amap.flutter.map.overlays.AbstractOverlayController;
import com.amap.flutter.map.utils.Const;
import com.amap.flutter.map.utils.ConvertUtil;
import com.amap.flutter.map.utils.LogUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

/**
 * @author whm
 * @date 2020/11/6 5:38 PM
 * @mail hongming.whm@alibaba-inc.com
 * @since
 */
public class MarkersController
        extends AbstractOverlayController<MarkerController>
        implements MyMethodCallHandler,
        AMap.OnMapClickListener,
        AMap.OnMarkerClickListener,
        AMap.OnMarkerDragListener,
        AMap.OnPOIClickListener,
        AMap.OnInfoWindowClickListener,
        AMap.InfoWindowAdapter {
    private static final String CLASS_NAME = "MarkersController";
    private String selectedMarkerDartId;
    private final TextureMapView mapView;

    public MarkersController(MethodChannel methodChannel, AMap amap, TextureMapView mapView) {
        super(methodChannel, amap);
        this.mapView = mapView;
        amap.addOnMarkerClickListener(this);
        amap.addOnMarkerDragListener(this);
        amap.addOnMapClickListener(this);
        amap.addOnPOIClickListener(this);
        amap.setOnInfoWindowClickListener(this);
        amap.setInfoWindowAdapter(this);
    }

    @Override
    public String[] getRegisterMethodIdArray() {
        return Const.METHOD_ID_LIST_FOR_MARKER;
    }


    @Override
    public void doMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        LogUtil.i(CLASS_NAME, "doMethodCall===>" + call.method);
        switch (call.method) {
            case Const.METHOD_MARKER_UPDATE:
                invokeMarkerOptions(call, result);
                break;
        }
    }


    /**
     * 执行主动方法更新marker
     *
     * @param methodCall
     * @param result
     */
    public void invokeMarkerOptions(MethodCall methodCall, MethodChannel.Result result) {
        if (null == methodCall) {
            return;
        }
        Object markersToAdd = methodCall.argument("markersToAdd");
        addByList((List<Object>) markersToAdd);
        Object markersToChange = methodCall.argument("markersToChange");
        updateByList((List<Object>) markersToChange);
        Object markerIdsToRemove = methodCall.argument("markerIdsToRemove");
        removeByIdList((List<Object>) markerIdsToRemove);
        result.success(null);
    }

    public void addByList(List<Object> markersToAdd) {
        if (markersToAdd != null) {
            for (Object markerToAdd : markersToAdd) {
                add(markerToAdd);
            }
        }
    }

    private void add(Object markerObj) {
        if (null != amap) {
            MarkerOptionsBuilder builder = new MarkerOptionsBuilder();
            String dartMarkerId = MarkerUtil.interpretMarkerOptions(markerObj, builder);
            if (!TextUtils.isEmpty(dartMarkerId)) {
                MarkerOptions markerOptions = builder.build();
                final Marker marker = amap.addMarker(markerOptions);
                Object clickable = ConvertUtil.getKeyValueFromMapObject(markerObj, "clickable");
                if (null != clickable) {
                    marker.setClickable(ConvertUtil.toBoolean(clickable));
                }
                MarkerController markerController = new MarkerController(marker);
                controllerMapByDartId.put(dartMarkerId, markerController);
                idMapByOverlyId.put(marker.getId(), dartMarkerId);
            }
        }

    }

    private void updateByList(List<Object> markersToChange) {
        if (markersToChange != null) {
            for (Object markerToChange : markersToChange) {
                update(markerToChange);
            }
        }
    }

    private void update(Object markerToChange) {
        Object dartMarkerId = ConvertUtil.getKeyValueFromMapObject(markerToChange, "id");
        if (null != dartMarkerId) {
            MarkerController markerController = controllerMapByDartId.get(dartMarkerId);
            if (null != markerController) {
                MarkerUtil.interpretMarkerOptions(markerToChange, markerController);
            }
        }
    }


    private void removeByIdList(List<Object> markerIdsToRemove) {
        if (markerIdsToRemove == null) {
            return;
        }
        for (Object rawMarkerId : markerIdsToRemove) {
            if (rawMarkerId == null) {
                continue;
            }
            String markerId = (String) rawMarkerId;
            final MarkerController markerController = controllerMapByDartId.remove(markerId);
            if (markerController != null) {

                idMapByOverlyId.remove(markerController.getMarkerId());
                markerController.remove();
            }
        }
    }

    private void showMarkerInfoWindow(String dartMarkId) {
        MarkerController markerController = controllerMapByDartId.get(dartMarkId);
        if (null != markerController) {
            markerController.showInfoWindow();
        }
    }

    private void hideMarkerInfoWindow(String dartMarkId, LatLng newPosition) {
        if (TextUtils.isEmpty(dartMarkId)) {
            return;
        }
        if (!controllerMapByDartId.containsKey(dartMarkId)) {
            return;
        }
        MarkerController markerController = controllerMapByDartId.get(dartMarkId);
        if (null != markerController) {
            if (null != newPosition && null != markerController.getPosition()) {
                if (markerController.getPosition().equals(newPosition)) {
                    return;
                }
            }
            markerController.hideInfoWindow();
        }
    }

    @Override
    public void onMapClick(LatLng latLng) {
        hideMarkerInfoWindow(selectedMarkerDartId, null);
    }

    @Override
    public boolean onMarkerClick(Marker marker) {
        String dartId = idMapByOverlyId.get(marker.getId());
        if (null == dartId) {
            return false;
        }
        final Map<String, Object> data = new HashMap<>(1);
        data.put("markerId", dartId);
        selectedMarkerDartId = dartId;
        showMarkerInfoWindow(dartId);
        methodChannel.invokeMethod("marker#onTap", data);
        LogUtil.i(CLASS_NAME, "onMarkerClick==>" + data);
        return true;
    }

    @Override
    public void onMarkerDragStart(Marker marker) {

    }

    @Override
    public void onMarkerDrag(Marker marker) {

    }

    @Override
    public void onMarkerDragEnd(Marker marker) {
        String markerId = marker.getId();
        String dartId = idMapByOverlyId.get(markerId);
        LatLng latLng = marker.getPosition();
        if (null == dartId) {
            return;
        }
        final Map<String, Object> data = new HashMap<>(2);
        data.put("markerId", dartId);
        data.put("position", ConvertUtil.latLngToList(latLng));
        methodChannel.invokeMethod("marker#onDragEnd", data);

        LogUtil.i(CLASS_NAME, "onMarkerDragEnd==>" + data);
    }

    @Override
    public void onPOIClick(Poi poi) {
        hideMarkerInfoWindow(selectedMarkerDartId, null != poi ? poi.getCoordinate() : null);
    }

    @Override
    public void onInfoWindowClick(Marker marker) {
        if (methodChannel == null || marker == null) {
            return;
        }
        String dartId = idMapByOverlyId.get(marker.getId());
        if (TextUtils.isEmpty(dartId)) {
            return;
        }
        final Map<String, Object> data = new HashMap<>(1);
        data.put("markerId", dartId);
        methodChannel.invokeMethod("infoWindow#onTap", data);
        LogUtil.i(CLASS_NAME, "onInfoWindowClick==>" + data);
    }

    @Override
    public View getInfoWindow(Marker marker) {
        // Check nulls
        if (mapView == null || marker == null) {
            return null;
        }
        
        // Force check: if no title/snippet, return null to avoid empty bubble
        if (TextUtils.isEmpty(marker.getTitle()) && TextUtils.isEmpty(marker.getSnippet())) {
            return null;
        }
        
        Context context = mapView.getContext();
        if (context == null) {
            return null;
        }

        // Create the main content container with transparent background
        LinearLayout contentContainer = new LinearLayout(context);
        contentContainer.setOrientation(LinearLayout.VERTICAL);
        contentContainer.setBackgroundColor(Color.TRANSPARENT); // Set transparent background
        int padding = dpToPx(context, 12);
        contentContainer.setPadding(padding, padding, padding, padding);

        // Set minimum and maximum width for adaptive sizing
        int minWidth = dpToPx(context, 80);  // Minimum width for bubble
        int maxWidth = dpToPx(context, 250); // Maximum width for bubble
        contentContainer.setMinimumWidth(minWidth);
        
        LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.WRAP_CONTENT, 
            LinearLayout.LayoutParams.WRAP_CONTENT
        );
        contentContainer.setLayoutParams(contentParams);

        // Custom background with rounded corners and shadow
        GradientDrawable bg = new GradientDrawable();
        bg.setColor(Color.WHITE);
        bg.setCornerRadius(dpToPx(context, 12));
        bg.setStroke(dpToPx(context, 1), 0x22000000);
        contentContainer.setBackground(bg);

        // Add title if present
        if (!TextUtils.isEmpty(marker.getTitle())) {
            TextView title = new TextView(context);
            title.setText(marker.getTitle());
            title.setTextColor(Color.parseColor("#000000"));
            title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); // 14sp for better readability
            title.setSingleLine(false); // allow multi-line wrap
            title.setTypeface(title.getTypeface(), android.graphics.Typeface.BOLD);
            title.setMaxWidth(maxWidth - padding * 2); // Set max width for text wrapping
            
            // Set layout parameters for title
            LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT, 
                LinearLayout.LayoutParams.WRAP_CONTENT
            );
            title.setLayoutParams(titleParams);
            contentContainer.addView(title);
        }

        // Add snippet if present
        if (!TextUtils.isEmpty(marker.getSnippet())) {
            TextView snippet = new TextView(context);
            snippet.setText(marker.getSnippet());
            snippet.setTextColor(Color.parseColor("#6D6D72"));
            snippet.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); // 12sp
            snippet.setSingleLine(false); // allow multi-line wrap
            snippet.setMaxWidth(maxWidth - padding * 2); // Set max width for text wrapping
            
            // Set layout parameters for snippet
            LinearLayout.LayoutParams snippetParams = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT, 
                LinearLayout.LayoutParams.WRAP_CONTENT
            );
            snippetParams.topMargin = dpToPx(context, 4);
            snippet.setLayoutParams(snippetParams);
            contentContainer.addView(snippet);
        }

        // Create the complete bubble wrapper with tail triangle
        LinearLayout bubbleWrapper = new LinearLayout(context);
        bubbleWrapper.setOrientation(LinearLayout.VERTICAL);
        bubbleWrapper.setPadding(0, 0, 0, 0);
        bubbleWrapper.setBackgroundColor(Color.TRANSPARENT); // Set transparent background for root
        bubbleWrapper.setClickable(true); // Make it fully clickable to override system
        
        // Set click listener to trigger info window click callback
        bubbleWrapper.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onInfoWindowClick(marker);
            }
        });
        
        // Set layout parameters for the complete bubble
        LinearLayout.LayoutParams wrapperParams = new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.WRAP_CONTENT, 
            LinearLayout.LayoutParams.WRAP_CONTENT
        );
        bubbleWrapper.setLayoutParams(wrapperParams);

        // Add the content container to the wrapper
        bubbleWrapper.addView(contentContainer);

        // Add tail triangle
        View triangle = new View(context);
        int triWidth = dpToPx(context, 16);
        int triHeight = dpToPx(context, 8);
        LinearLayout.LayoutParams triParams = new LinearLayout.LayoutParams(triWidth, triHeight);
        triParams.gravity = android.view.Gravity.CENTER_HORIZONTAL;
        triangle.setLayoutParams(triParams);
        triangle.setBackground(new TriangleDrawable(Color.WHITE, 0x22000000, dpToPx(context, 1)));
        bubbleWrapper.addView(triangle);

        return bubbleWrapper;
    }

    @Override
    public View getInfoContents(Marker marker) {
        // Never provide info contents when we have custom info window
        // This ensures no double layering with default bubble frame
        return null;
    }

    private int dpToPx(Context context, float dp) {
        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()));
    }
}
